package scanner

import (
	"context"
	"time"

	"gitee.com/mkwchecking/sr-admin/pkg/file/visit"
	"github.com/go-kratos/kratos/v2/log"
)

func NewScanJob(opt *ScanOption, handler IFileHandler) *scanJob {
	job := &scanJob{
		opt:     opt,
		handler: handler,
		visitor: visit.NewVisitor(
			visit.WithExcludeExt(opt.ExcludeExts...),
			visit.WithIncludeExt(opt.Exts...),
		),
		total:       0,
		handleQueue: make(chan *visit.FileInfo, opt.HandleQueueLen),
		state: &ScanState{
			Status:   ScanStatusIdle,
			CreateAt: time.Now(),
			Option:   *opt,
		},
	}

	return job
}

type ScanStatus int

const (
	ScanStatusIdle ScanStatus = iota
	ScanStatusScanning
	ScanStatusFinished
)

type ScanState struct {
	Option   ScanOption // 扫描选项
	Status   ScanStatus // 扫描状态
	Error    error      // 错误
	CreateAt time.Time  // 创建时间
	FinishAt time.Time  // 完成时间
}

type scanJob struct {
	ctx         context.Context
	opt         *ScanOption          // 扫描选项
	handler     IFileHandler         // 文件处理器
	visitor     visit.IFolderVisitor // 文件夹遍历器
	total       int                  // 总文件数
	handleQueue chan *visit.FileInfo // 处理队列
	state       *ScanState
}

func (s *scanJob) GetState() *ScanState {
	return s.state
}

// Execute implements job.JobExec.
func (s *scanJob) Execute(ctx context.Context) error {

	ct, cancel := context.WithCancel(ctx)
	go s.handle(ct)

	err := s.scan(ct)
	if err != nil {
		cancel()
		return err
	} else {
		cancel()
	}

	return nil
}

func (s *scanJob) scan(ctx context.Context) error {
	s.ctx = ctx
	s.state.Status = ScanStatusScanning

	err := s.visitor.VisitFolders(s.opt.Paths, s.onReceiveVisitFile)
	if err != nil {
		s.state.Error = err
	}

	s.state.Status = ScanStatusFinished
	s.state.FinishAt = time.Now()
	if s.handler != nil {
		s.handler.OnFinished(ctx, *s.opt)
	}
	return err
}

// onReceiveVisitFile 接收遍历到的文件
func (s *scanJob) onReceiveVisitFile(path string, info visit.FileInfo) error {
	log.Debug("scan file", "path", path)
	s.total++
	s.handleQueue <- &info
	return nil
}

func (s *scanJob) handle(ctx context.Context) error {

	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case info := <-s.handleQueue:
			if err := s.handleFile(info); err != nil {
				s.state.Error = err
				return err
			}
		}
	}

}

func (s *scanJob) handleFile(file *visit.FileInfo) error {
	base := BaseAny{
		RootPath: file.RootPath,
		Path:     file.Path,
		Name:     file.Name,
	}

	if file.IsDir {
		return s.handler.OnHandleFolder(&base)
	}

	baseFile := &BaseFile{
		BaseAny:    base,
		Ext:        file.Ext,
		Size:       file.Size,
		Hash:       file.Hash,
		FileType:   file.FileType,
		ModTime:    file.ModTime,
		Author:     "",
		OriginPath: file.Path,
	}

	switch file.FileType {
	case visit.FileTypeVideo:
		videoFile := &VideoFile{
			BaseFile: *baseFile,
		}
		return s.handler.OnHandleVideoFile(videoFile)
	case visit.FileTypeImage:
		imageFile := &ImageFile{
			BaseFile: *baseFile,
		}
		return s.handler.OnHandleImageFile(imageFile)
	case visit.FileTypePPT, visit.FileTypePDF, visit.FileTypeWord, visit.FileTypeExcel, visit.FileTypeMarkdown, visit.FileTypeText, visit.FileTypeCode:
		pptFile := &DocFile{
			BaseFile: *baseFile,
		}
		return s.handler.OnHandleDocFile(pptFile)
	default:
		return s.handler.OnHandleFile(baseFile)
	}
}
